//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
//                                                                        \\
//                 Centre for Speech Technology Research                  \\
//                      University of Edinburgh, UK                       \\
//                        Copyright (c) 1996,1997                         \\
//                         All Rights Reserved.                           \\
//   Permission is hereby granted, free of charge, to use and distribute  \\
//   this software and its documentation without restriction, including   \\
//   without limitation the rights to use, copy, modify, merge, publish,  \\
//   distribute, sublicense, and/or sell copies of this work, and to      \\
//   permit persons to whom this work is furnished to do so, subject to   \\
//   the following conditions:                                            \\
//    1. The code must retain the above copyright notice, this list of    \\
//       conditions and the following disclaimer.                         \\
//    2. Any modifications must be clearly marked as such.                \\
//    3. Original authors' names are not deleted.                         \\
//    4. The authors' names are not used to endorse or promote products   \\
//       derived from this software without specific prior written        \\
//       permission.                                                      \\
//   THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK        \\
//   DISCLAIM ALL WARRANTIES With REGARD TO THIS SOFTWARE, INCLUDING      \\
//   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT   \\
//   SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE     \\
//   FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    \\
//   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN   \\
//   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,          \\
//   ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF       \\
//   THIS SOFTWARE.                                                       \\
//                                                                        \\
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
//                                                                        \\
//                  Author: Richard Caley (rjc@cstr.ed.ac.uk)             \\
//  --------------------------------------------------------------------  \\
//  The thread which actually communicates with festival.                 \\
//                                                                        \\
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\

package interactivespaces.service.speech.synthesis.internal.festival.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

class Festival extends Thread {
	public static final int FT_SCHEME = 1;
	public static final int FT_WAVE = 2;

	protected static byte[] endm;
	protected Socket s;
	protected String hostname = null;
	protected InetAddress address = null;
	protected int port = -1;
	protected boolean closeOnExit;

	protected PrintWriter out;
	protected InputStream in;

	protected boolean ok;

	protected JobQueue jobs;

	private byte buffer[];
	private int buffered_p;
	private int buffered_e;

	static {
		String end = "ft_StUfF_key";
		endm = new byte[end.length()];

		for (int i = 0; i < endm.length; i++) {
			endm[i] = (byte) end.charAt(i);
		}

	}

	protected Festival(Socket sock, boolean close) {
		s = sock;
		closeOnExit = close;
		jobs = new JobQueue();
		buffer = new byte[8096];
		buffered_p = 0;
		buffered_e = 0;
		ok = true;
	}

	public Festival(Socket sock) {
		this(sock, false);
	}

	public Festival(InetAddress addr, int p) throws IOException {
		this(new Socket(addr, p), true);
		address = addr;
		port = p;
	}

	public Festival(String host, int p) throws IOException,
			UnknownHostException {
		this(InetAddress.getByName(host), p);
		hostname = host;
	}

	public void run() {
		try {
			setPriority(getPriority() + 1);
			out = new PrintWriter(s.getOutputStream());
			in = s.getInputStream();
			while (true) {
				if (jobs.isEmpty()) {
					if (ok)
						suspend();
					else
						break;
				} else {
					Job job = (Job) jobs.get();
					// System.out.println("sending "+job.command);
					out.println(job.command);
					out.flush();

					String status;

					job.session.notifyRunning(job.id);

					while (true) {
						status = getStatus();
						if (status.startsWith("ER")) {
							job.session.notifyError(job.id, "");
							break;
						} else if (status.startsWith("LP")) {
							byte[] res = getResult();
							job.session.notifyResult(job.id, FT_SCHEME, res);
						} else if (status.startsWith("WV")) {
							byte[] res = getResult();
							job.session.notifyResult(job.id, FT_WAVE, res);
						} else if (status.startsWith("OK")) {
							break;
						} else {
							byte[] res = getResult();
							job.session.notifyError(job.id, "unknown type");
						}
					}

					job.session.notifyFinished(job.id);
				}
			}
		} catch (IOException ex) {
			System.err.println("IO Error '" + ex.getMessage() + "'");
		} finally {
			try {
				closeSocket();
			} catch (IOException ex) {
				System.err.println("Error closing festival socket '"
						+ ex.getMessage() + "'");
			}
			while (!jobs.isEmpty()) {
				System.out.println("Left Job '" + ((Job) jobs.get()).command
						+ "'");
			}
		}
	}

	public void connect() {
		// System.err.println("Connecting");
		start();
	}

	public void disconnect(boolean carefully) {
		// System.err.println("Disconnecting");
		ok = false;
		resume();
		if (carefully) {
			try {
				join();
			} catch (InterruptedException ex) {
				stop();
			}
			;
		} else
			stop();
	}

	private void closeSocket() throws IOException {
		if (closeOnExit) {
			// System.err.println("Closing festival socket");
			s.close();
		}
	}

	public void newJob(Integer id, String c, Session s) {
		jobs.add(new Job(id, c, s));
		resume();
	}

	// finally! To the things which talk to festival.

	private byte[] readTo(InputStream s, char end) throws IOException {
		if (buffered_e == buffered_p) {
			buffered_p = 0;
			buffered_e = s.read(buffer, 0, 8096);
		}

		byte[] res = null;
		for (int i = buffered_p; i < buffered_e; i++)
			if (buffer[i] == (byte) end) {
				res = new byte[i - buffered_p + 1];
				break;
			}

		if (res == null)
			res = new byte[buffered_e - buffered_p];

		for (int i = 0; i < res.length; i++)
			res[i] = buffer[buffered_p + i];

		buffered_p = buffered_p + res.length;

		return res;
	}

	protected String getLine() throws IOException {
		StringBuffer line = new StringBuffer(20);

		while (true) {
			byte[] bit = readTo(in, '\n');
			line.append(new String(bit));

			// System.out.println("got '"+line.toString()+"'");

			if (line.charAt(line.length() - 1) == '\n')
				break;
		}

		// System.out.println("result '"+line.toString()+"'");
		return line.toString();
	}

	protected String getStatus() throws IOException {
		String line = getLine();

		// System.out.println("        Status = '"+line+"'");
		return line == null ? "EOF" : line;
	}

	protected byte[] getResult() throws IOException {
		byte[] res = new byte[0];

		while (true) {
			byte[] bit = readTo(in, 'y');

			int end = bit.length - endm.length;

			if (end < 0)
				end = bit.length;
			else {
				for (int i = 0; i < endm.length; i++)
					if (bit[i + end] != endm[i]) {
						end = bit.length;
						break;
					}
			}

			byte[] new_res = new byte[res.length + end];

			for (int i = 0; i < res.length; i++)
				new_res[i] = res[i];
			for (int i = 0; i < end; i++)
				new_res[res.length + i] = bit[i];

			res = new_res;

			if (end < bit.length)
				break;
		}

		return res;
	}

	private static class Job {
		Integer id;
		String command;
		Session session;

		public Job(Integer i, String c, Session s) {
			id = i;
			command = c;
			session = s;
		}
	}
}
